package org.springframework.boot.netty.argumentResolver;

import com.google.protobuf.util.JsonFormat;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.autoconfigure.netty.NettyProperties;
import org.springframework.boot.autoconfigure.netty.enums.StartUpMode;
import org.springframework.boot.netty.codec.ProtoBufCodecBean;
import org.springframework.boot.netty.exception.ConvertException;
import org.springframework.boot.netty.listener.Message;
import org.springframework.boot.netty.support.JsonUtil;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Author: huoxingzhi
 * Date: 2020/12/16
 * Email: hxz_798561819@163.com
 */
public class ChannelAutoProcessMethodArgumentResolver extends ReturnValueArgumentResolverAdapter implements SmartInitializingSingleton {

    private NettyProperties cachedNettyProperties;



    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        Method method = methodParameter.getMethod();
        Class<?> returnType = method.getReturnType();
        return !returnType.isAssignableFrom(void.class)
                &&
                !this.cachedNettyProperties.getStartUpMode().equals(StartUpMode.HTTP);
    }

    @Override
    public void resolveArgument(Message message, Object returnValue, MethodParameter methodParameter) throws Exception {

        Channel currentChannel = message.getCurrentChannel();
        StartUpMode startUpMode = this.cachedNettyProperties.getStartUpMode();

        if(null==currentChannel){
            super.resolveArgument(message, returnValue, methodParameter);
        }


        if(startUpMode.equals(StartUpMode.WEBSOCKET)){

            webSocketMessageToChannel(returnValue, currentChannel);
        }

        if(startUpMode.equals(StartUpMode.SERVER) || startUpMode.equals(StartUpMode.CLINET)){

            if(returnValue instanceof Map){
                Class convertedClass = (Class) ((Map) returnValue).get("class");
                Object content = ((Map) returnValue).get("payload");
                String accessToken = (String) ((Map) returnValue).get("access_token");
                protoBufMessageSendToChannel(currentChannel, convertedClass, content,accessToken);
            }

        }

    }

    private void webSocketMessageToChannel(Object returnValue, Channel currentChannel) {
        String convertedValue = null;

        if(this.conversionService.canConvert(returnValue.getClass(),String.class)){
            convertedValue = this.conversionService.convert(returnValue,String.class);
        }

        if(null==convertedValue){
            convertedValue = JsonUtil.obj2String(returnValue);
        }

        currentChannel.writeAndFlush(new TextWebSocketFrame(convertedValue));
    }


    private void protoBufMessageSendToChannel( Channel currentChannel, Class convertedClass, Object content,String accessToken) throws Exception {
        com.google.protobuf.Message.Builder builder;
        if(!(content instanceof com.google.protobuf.Message)){
            return;
        }

        try {
            Method method = convertedClass.getMethod("newBuilder", new Class[0]);
            builder = (com.google.protobuf.Message.Builder) method.invoke(convertedClass, new Object[0]);
            JsonFormat.parser()
                    .ignoringUnknownFields()
                    .merge(JsonUtil.obj2String(content), builder);

            com.google.protobuf.Message returnMessage = builder.build();

            Map<String,String> headers = new HashMap<>();
            headers.put("class",convertedClass.getSimpleName());
            headers.put("access_token",accessToken);
            ProtoBufCodecBean protoBufCodecBean = new ProtoBufCodecBean();
            protoBufCodecBean.setMessage(returnMessage);
            protoBufCodecBean.setHeaders(headers);

            currentChannel.writeAndFlush(protoBufCodecBean);

        } catch (Exception e) {
            throw new ConvertException(content+" converted com.google.protobuf.Message failed");
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        super.setApplicationContext(applicationContext);
    }

    @Override
    public void afterSingletonsInstantiated() {
        this.cachedNettyProperties = this.applicationContext.getBean(NettyProperties.class);
    }
}
